{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d0b64d66-c3e4-4da8-9736-b05bf9a32409",
   "metadata": {},
   "source": [
    "# The Project\n",
    "- QNN for XOR Problem:\n",
    "\n",
    "  Classiq has an available dataset for training PQC to imitate the XOR gate, similar to how we trained a U-gate to act as a NOT gate. Design a QNN to solve the XOR problem. Read more on the dataset [here](https://docs.classiq.io/latest/reference-manual/built-in-algorithms/qml/qnn/datasets/#datasetxor).\n",
    "\n",
    "  Click [here](https://en.wikipedia.org/wiki/XOR_gate) for more information about XOR gates\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b17bf52c-0cda-49ff-a883-0f090061f2e3",
   "metadata": {},
   "source": [
    "## Setting The Scene"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45633e16-7f1d-4e4f-9346-5524091ed072",
   "metadata": {},
   "source": [
    "When running from Google Colab, we need to install Classiq's SDK and authenticate the remote device:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "161ccbd8-538b-4589-a168-e981b49c7b50",
   "metadata": {},
   "outputs": [],
   "source": [
    "#!pip install classiq numpy torchvision torch\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "4dffbfa1-4e29-4896-84a4-cb38592ca748",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import classiq\n",
    "# classiq.authenticate(overwrite=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61c6620f-a42c-44ac-89d7-67a2a259f975",
   "metadata": {},
   "source": [
    "# Step 1 - Create our `torch.nn.Module`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "630ba942-3e8f-4eab-88b7-1638fb26f977",
   "metadata": {
    "id": "dae15b1c-0d3f-4807-ae48-edfba8425962"
   },
   "source": [
    "## Step 1.1 - Create Dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d973e3d-b381-4607-80c1-f669e256e4f7",
   "metadata": {
    "id": "5105b43a-340e-4f79-8d54-f6e898ba415c"
   },
   "source": [
    "We will use the DATALOADER_NOT dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 127,
   "id": "d644402d-4f61-41b6-8427-b45d0c5df1b0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--> Data for training:\n",
      "tensor([[0., 1.],\n",
      "        [1., 1.],\n",
      "        [0., 0.],\n",
      "        [1., 0.]])\n",
      "--> Corresponding labels:\n",
      "tensor([1., 0., 0., 1.])\n"
     ]
    }
   ],
   "source": [
    "\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import DataLoader, Dataset\n",
    "# XORゲートのデータセット\n",
    "from torch.utils.data import DataLoader\n",
    "from classiq.applications.qnn.datasets import DATALOADER_XOR\n",
    "\n",
    "for data, label in DATALOADER_XOR:\n",
    "    print(f\"--> Data for training:\\n{data}\")\n",
    "    print(f\"--> Corresponding labels:\\n{label}\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b015f2a7-4641-4fc8-b6d1-8e0b8d2b751c",
   "metadata": {
    "id": "dae15b1c-0d3f-4807-ae48-edfba8425962"
   },
   "source": [
    "## Step 1.2 - Create our parametric quantum program"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35de5a77-06b2-40c3-97cb-07352de1bc7c",
   "metadata": {},
   "source": [
    "Our quantum model will be defined and synthesized as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 128,
   "id": "fb04f41e-0016-4cd3-a798-9a36321a49cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq import *\n",
    "\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "\n",
    "# 量子プログラムの作成\n",
    "@qfunc\n",
    "def encoding(input_0: CInt, input_1: CInt, q: QArray[QBit]) -> None:\n",
    "    RX(theta=input_0*np.pi, target=q[0])\n",
    "    RX(theta=input_1*np.pi, target=q[1])\n",
    "\n",
    "\n",
    "@qfunc\n",
    "def mixing(theta: CReal, q: QArray[QBit]) -> None:\n",
    "    CRX(theta, q[1], q[0])\n",
    "\n",
    "@qfunc\n",
    "def main(input_0: CReal, input_1: CReal, weight_0: CReal, weight_1: CReal, res: Output[QArray[QBit]]) -> None:\n",
    "    allocate(2, res)\n",
    "    encoding(input_0, input_1, res)  # Loading input\n",
    "    mixing(weight_0, res)   # Adjustable parameter\n",
    "\n",
    "\n",
    "model = create_model(main)\n",
    "quantum_program = synthesize(model)\n",
    "\n",
    "\n",
    "#show(quantum_program)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3615654e-fed1-4bfd-bf9d-318557de79ad",
   "metadata": {
    "id": "bef15105-d942-45a6-a615-7b5458241703"
   },
   "source": [
    "## Step 1.3 - Create the Execution and Post-processing"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a10a9b0-d994-4d67-a071-157898793c2e",
   "metadata": {
    "id": "69875135-2a3d-4d8a-91df-ee501f01eac2"
   },
   "source": [
    "The following example defines a function that takes in a parametric quantum program plus parameters, executes the program, and returns the result. Notes:\n",
    "\n",
    "1. The code can be executed on a physical computer or on a simulator. In any case, implement the execution using `execute_qnn`.\n",
    "2. Post-process the result of the execution to obtain a single number (`float`) and a single dimension `Tensor`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 183,
   "id": "0ec5e0af-a44f-44c4-a7af-7c256858364e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq.applications.qnn.types import (\n",
    "    MultipleArguments,\n",
    "    SavedResult,\n",
    "    ResultsCollection,\n",
    ")\n",
    "\n",
    "from classiq.execution import execute_qnn\n",
    "from classiq.synthesis import SerializedQuantumProgram\n",
    "\n",
    "from classiq.applications.qnn.datasets import DATALOADER_NOT\n",
    "import torch\n",
    "\n",
    "# 実行およびポストプロセス関数の作成\n",
    "def execute(\n",
    "    quantum_program: SerializedQuantumProgram, arguments: MultipleArguments\n",
    ") -> ResultsCollection:\n",
    "    return execute_qnn(quantum_program, arguments)\n",
    "\n",
    "def post_process(result: SavedResult) -> torch.Tensor:\n",
    "    \"\"\"\n",
    "    \n",
    "    probability of measuring \n",
    "    p_one  |01> + |11> \n",
    "    p_zero |00> + |10>\n",
    "    \"\"\"\n",
    "    counts: dict = result.value.counts\n",
    "    p_one:float = counts.get(\"01\", 0.0) / sum(counts.values()) + counts.get(\"11\", 0.0) / sum(counts.values()) \n",
    "    p_zero:float = counts.get(\"00\", 0.0) / sum(counts.values()) + counts.get(\"10\", 0.0) / sum(counts.values())\n",
    "    p_xor: float = p_one - p_zero\n",
    "    \n",
    "    return torch.tensor(0.0 if p_xor < 0 else p_xor)\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69934d9a-1245-4739-b267-d8eef8489f8a",
   "metadata": {
    "id": "aa6a64b5-bdff-4e66-b16c-6ede1d69877d"
   },
   "source": [
    "## Step 1.4 - Create a network"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed1b92e8-a8f0-411a-9831-2398599adb84",
   "metadata": {
    "id": "1937b4d5-abfa-40bd-b26e-32c93936be18"
   },
   "source": [
    "Now we're going to define a network, just like any other PyTorch network, only that this time, we will have only 1 layer, and it will be a quantum layer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 205,
   "id": "b11021d4-bc80-4421-a8f3-e635b0a6fc17",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "from classiq.applications.qnn import QLayer\n",
    "# ネットワークの作成\n",
    "\n",
    "class Net(torch.nn.Module):\n",
    "    def __init__(self, *args, **kwargs) -> None:\n",
    "        super().__init__()\n",
    "        self.qlayer = QLayer(\n",
    "            quantum_program,  # the quantum program, the result of `synthesize()`\n",
    "            execute,  # a callable that takes\n",
    "            # - a quantum program\n",
    "            # - parameters to that program (a tuple of dictionaries)\n",
    "            # and returns a `ResultsCollection`\n",
    "            post_process,  # a callable that takes\n",
    "            # - a single `SavedResult`\n",
    "            # and returns a `torch.Tensor`\n",
    "            *args,\n",
    "            **kwargs\n",
    "        )\n",
    "        \n",
    "\n",
    "    def forward(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        # return the new parameter\n",
    "        return self.qlayer(x)\n",
    "\n",
    "model = Net()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7600a642-b863-4eb8-9b8b-6040482b6b4f",
   "metadata": {
    "id": "a94ff1bc-58f5-4743-aa09-0cc77dcfecd6"
   },
   "source": [
    "# Step 2 - Choose a dataset, loss function, and optimizer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69e3927a-4276-4c1d-b5ed-a343e75a73dc",
   "metadata": {
    "id": "8ac9fb68-bbf5-48c1-ba7a-2af0d9219b74"
   },
   "source": [
    "We will use optimizer,defined [here](https://docs.classiq.io/latest/reference-manual/built-in-algorithms/qml/qnn/datasets/) as well as [L1Loss](https://pytorch.org/docs/stable/generated/torch.nn.L1Loss.html) and [SGD](https://pytorch.org/docs/stable/generated/torch.optim.SGD.html)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 206,
   "id": "24ac42c2-31a8-4726-a065-1f88b7be3744",
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq.applications.qnn.datasets import DATALOADER_NOT\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from classiq.applications.qnn.datasets import DATALOADER_XOR\n",
    "\n",
    "data_loader=DATALOADER_XOR        \n",
    "\n",
    "# 損失関数とオプティマイザの定義\n",
    "loss_func = nn.L1Loss()     # Mean Absolute Error (MAE)\n",
    "optimizer = optim.SGD(model.parameters(), lr=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09aa079e-5edd-40a6-ab24-5acb891da094",
   "metadata": {
    "id": "b0087ab6-addd-41ac-b609-f60b0b4591cd"
   },
   "source": [
    "# Step 3 - Train"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca05190d-dde4-4f33-8389-5a85aa70ca23",
   "metadata": {
    "id": "ae072cf7-431f-4583-b5c7-a21be4ef56f2"
   },
   "source": [
    "For the training process, we will use a loop similar to [the one recommended by PyTorch](https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html#update-the-weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 207,
   "id": "26b387e2-75d1-49b0-800e-9f87586d5c1b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 Parameter containing:\n",
      "tensor([0.7832], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.048828125\n",
      "Epoch 1/100, Loss: 0.427001953125\n",
      "1 Parameter containing:\n",
      "tensor([0.8320], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.18310546875\n",
      "Epoch 2/100, Loss: 0.419677734375\n",
      "2 Parameter containing:\n",
      "tensor([1.0151], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.35400390625\n",
      "Epoch 3/100, Loss: 0.376708984375\n",
      "3 Parameter containing:\n",
      "tensor([1.3691], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.732421875\n",
      "Epoch 4/100, Loss: 0.299560546875\n",
      "4 Parameter containing:\n",
      "tensor([2.1016], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.30517578125\n",
      "Epoch 5/100, Loss: 0.119140625\n",
      "5 Parameter containing:\n",
      "tensor([2.4067], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.52490234375\n",
      "Epoch 6/100, Loss: 0.059814453125\n",
      "6 Parameter containing:\n",
      "tensor([2.9316], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.09765625\n",
      "Epoch 7/100, Loss: 0.00390625\n",
      "7 Parameter containing:\n",
      "tensor([3.0293], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0244140625\n",
      "Epoch 8/100, Loss: 0.00146484375\n",
      "8 Parameter containing:\n",
      "tensor([3.0049], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.06103515625\n",
      "Epoch 9/100, Loss: 0.001708984375\n",
      "9 Parameter containing:\n",
      "tensor([2.9438], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 10/100, Loss: 0.00537109375\n",
      "10 Parameter containing:\n",
      "tensor([2.9438], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.08544921875\n",
      "Epoch 11/100, Loss: 0.00390625\n",
      "11 Parameter containing:\n",
      "tensor([2.8584], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.1220703125\n",
      "Epoch 12/100, Loss: 0.01123046875\n",
      "12 Parameter containing:\n",
      "tensor([2.7363], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.06103515625\n",
      "Epoch 13/100, Loss: 0.021240234375\n",
      "13 Parameter containing:\n",
      "tensor([2.7974], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.10986328125\n",
      "Epoch 14/100, Loss: 0.015625\n",
      "14 Parameter containing:\n",
      "tensor([2.6875], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.10986328125\n",
      "Epoch 15/100, Loss: 0.02392578125\n",
      "15 Parameter containing:\n",
      "tensor([2.7974], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0244140625\n",
      "Epoch 16/100, Loss: 0.01611328125\n",
      "16 Parameter containing:\n",
      "tensor([2.8218], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0732421875\n",
      "Epoch 17/100, Loss: 0.013427734375\n",
      "17 Parameter containing:\n",
      "tensor([2.8950], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.1220703125\n",
      "Epoch 18/100, Loss: 0.007568359375\n",
      "18 Parameter containing:\n",
      "tensor([3.0171], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0244140625\n",
      "Epoch 19/100, Loss: 0.001220703125\n",
      "19 Parameter containing:\n",
      "tensor([3.0415], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.01220703125\n",
      "Epoch 20/100, Loss: 0.00146484375\n",
      "20 Parameter containing:\n",
      "tensor([3.0537], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 21/100, Loss: 0.001220703125\n",
      "21 Parameter containing:\n",
      "tensor([3.0537], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.03662109375\n",
      "Epoch 22/100, Loss: 0.001220703125\n",
      "22 Parameter containing:\n",
      "tensor([3.0171], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.048828125\n",
      "Epoch 23/100, Loss: 0.001708984375\n",
      "23 Parameter containing:\n",
      "tensor([2.9683], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.048828125\n",
      "Epoch 24/100, Loss: 0.00439453125\n",
      "24 Parameter containing:\n",
      "tensor([2.9194], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.08544921875\n",
      "Epoch 25/100, Loss: 0.005615234375\n",
      "25 Parameter containing:\n",
      "tensor([2.8340], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.048828125\n",
      "Epoch 26/100, Loss: 0.01220703125\n",
      "26 Parameter containing:\n",
      "tensor([2.7851], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.01220703125\n",
      "Epoch 27/100, Loss: 0.016357421875\n",
      "27 Parameter containing:\n",
      "tensor([2.7974], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.3173828125\n",
      "Epoch 28/100, Loss: 0.012451171875\n",
      "28 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 29/100, Loss: 0.0\n",
      "29 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 30/100, Loss: 0.0\n",
      "30 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 31/100, Loss: 0.0\n",
      "31 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 32/100, Loss: 0.000244140625\n",
      "32 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 33/100, Loss: 0.0\n",
      "33 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 34/100, Loss: 0.0\n",
      "34 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 35/100, Loss: 0.000244140625\n",
      "35 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 36/100, Loss: 0.0\n",
      "36 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 37/100, Loss: 0.0\n",
      "37 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 38/100, Loss: 0.0\n",
      "38 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 39/100, Loss: 0.0\n",
      "39 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 40/100, Loss: 0.0\n",
      "40 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 41/100, Loss: 0.0\n",
      "41 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 42/100, Loss: 0.0\n",
      "42 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 43/100, Loss: 0.0\n",
      "43 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 44/100, Loss: 0.000244140625\n",
      "44 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 45/100, Loss: 0.0\n",
      "45 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 46/100, Loss: 0.000244140625\n",
      "46 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 47/100, Loss: 0.0\n",
      "47 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 48/100, Loss: 0.000244140625\n",
      "48 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 49/100, Loss: 0.0\n",
      "49 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 50/100, Loss: 0.0\n",
      "50 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 51/100, Loss: 0.0\n",
      "51 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 52/100, Loss: 0.0\n",
      "52 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 53/100, Loss: 0.0\n",
      "53 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 54/100, Loss: 0.000244140625\n",
      "54 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 55/100, Loss: 0.0\n",
      "55 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 56/100, Loss: 0.0\n",
      "56 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 57/100, Loss: 0.000244140625\n",
      "57 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 58/100, Loss: 0.000244140625\n",
      "58 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 59/100, Loss: 0.000244140625\n",
      "59 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 60/100, Loss: 0.0\n",
      "60 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 61/100, Loss: 0.0\n",
      "61 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 62/100, Loss: 0.0\n",
      "62 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 63/100, Loss: 0.000244140625\n",
      "63 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 64/100, Loss: 0.0\n",
      "64 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 65/100, Loss: 0.000244140625\n",
      "65 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 66/100, Loss: 0.000244140625\n",
      "66 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 67/100, Loss: 0.000244140625\n",
      "67 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 68/100, Loss: 0.0\n",
      "68 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 69/100, Loss: 0.000244140625\n",
      "69 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 70/100, Loss: 0.0\n",
      "70 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 71/100, Loss: 0.000244140625\n",
      "71 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 72/100, Loss: 0.0\n",
      "72 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 73/100, Loss: 0.0\n",
      "73 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 74/100, Loss: 0.0\n",
      "74 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 75/100, Loss: 0.000244140625\n",
      "75 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 76/100, Loss: 0.0\n",
      "76 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 77/100, Loss: 0.0\n",
      "77 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 78/100, Loss: 0.0\n",
      "78 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 79/100, Loss: 0.0\n",
      "79 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 80/100, Loss: 0.0\n",
      "80 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 81/100, Loss: 0.0\n",
      "81 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 82/100, Loss: 0.0\n",
      "82 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 83/100, Loss: 0.0\n",
      "83 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 84/100, Loss: 0.0\n",
      "84 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 85/100, Loss: 0.0\n",
      "85 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 86/100, Loss: 0.0\n",
      "86 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 87/100, Loss: 0.000244140625\n",
      "87 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 88/100, Loss: 0.0\n",
      "88 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 89/100, Loss: 0.0\n",
      "89 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 90/100, Loss: 0.0\n",
      "90 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 91/100, Loss: 0.0\n",
      "91 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 92/100, Loss: 0.0\n",
      "92 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 93/100, Loss: 0.000244140625\n",
      "93 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 94/100, Loss: 0.000244140625\n",
      "94 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 95/100, Loss: 0.0\n",
      "95 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 96/100, Loss: 0.0\n",
      "96 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 97/100, Loss: 0.000244140625\n",
      "97 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 98/100, Loss: 0.0\n",
      "98 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 99/100, Loss: 0.000244140625\n",
      "99 Parameter containing:\n",
      "tensor([3.1147], requires_grad=True)\n",
      "Grad for qlayer.weight: 0.0\n",
      "Gradient for qlayer.weight is zero.\n",
      "Epoch 100/100, Loss: 0.0\n",
      "Trained model saved to trained_model.pth\n"
     ]
    }
   ],
   "source": [
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "# 学習およびテストの準備\n",
    "def train(model, data_loader, loss_func, optimizer, epochs=100):\n",
    "    model.train()\n",
    "    for epoch in range(epochs):\n",
    "        total_loss = 0\n",
    "        print(epoch, model.qlayer.weight)\n",
    "        for data, label in data_loader:\n",
    "            optimizer.zero_grad()\n",
    "            output = model(data)\n",
    "            output = output.view(-1)  # 出力の形状を修正\n",
    "            label = label.view(-1)    # ラベルの形状を修正\n",
    "            loss = loss_func(output, label)\n",
    "            loss.backward()\n",
    "\n",
    "            # 勾配チェックを追加して問題を診断\n",
    "            for name, param in model.named_parameters():\n",
    "                if param.grad is not None:\n",
    "                    print(f'Grad for {name}: {param.grad.norm().item()}')\n",
    "                else:\n",
    "                    print(f'No grad for {name}')\n",
    "                if param.grad is not None and torch.all(param.grad == 0):\n",
    "                    print(f'Gradient for {name} is zero.')\n",
    "\n",
    "            optimizer.step()\n",
    "            total_loss += loss.item()\n",
    "        print(f'Epoch {epoch + 1}/{epochs}, Loss: {total_loss / len(data_loader)}')\n",
    "\n",
    "    # 学習後のモデルを保存\n",
    "    torch.save(model.state_dict(), './trained_model.pth')\n",
    "    print(\"Trained model saved to trained_model.pth\")\n",
    "train(model, data_loader, loss_func, optimizer, epochs=100)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a79f438-539d-4412-a839-45c0cb29b4a9",
   "metadata": {
    "id": "61fa4f83-8bd2-4290-9ca4-f6c5c1ca0e32"
   },
   "source": [
    "# Step 4 - Test"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ccce1b93-7895-4bcd-95dd-330c0ff8669b",
   "metadata": {
    "id": "7533b162-6214-4606-9e66-ec3332a2f446"
   },
   "source": [
    "Lastly, we will test our network accuracy, using [the following answer](https://stackoverflow.com/questions/52176178/pytorch-model-accuracy-test#answer-64838681)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 208,
   "id": "34872f7b-45ab-4d65-ba16-8176c8d7807a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "predictions: tensor([1., 0., 1., 0.], requires_grad=True)\n",
      "labels:      tensor([1., 0., 1., 0.])\n",
      "num_correct: 4, total: 4\n",
      "Test Accuracy of the model: 100.00%\n",
      "Data: tensor([[1., 0.],\n",
      "        [0., 0.],\n",
      "        [1., 1.],\n",
      "        [0., 1.]]), Label: tensor([1., 0., 0., 1.]), Output: tensor([1., 0., 0., 1.], grad_fn=<QLayerFunctionBackward>), Loss: 0.0\n"
     ]
    }
   ],
   "source": [
    "# テスト関数\n",
    "def check_accuracy(model: nn.Module, data_loader: DataLoader, atol=1e-4) -> float:\n",
    "    num_correct = 0\n",
    "    total = 0\n",
    "    model.eval()\n",
    "\n",
    "    with torch.no_grad():                        # temporarily disable gradient calculation\n",
    "        for data, labels in data_loader:\n",
    "            # let the model predict\n",
    "            predictions = model(data)\n",
    "            print('predictions:', predictions)\n",
    "            print('labels:     ', labels)\n",
    "\n",
    "            # get a tensor of booleans, indicating if each label is close to the real label\n",
    "            is_prediction_correct = predictions.isclose(labels, atol=atol)\n",
    "\n",
    "            # count the amount of `True` predictions\n",
    "            num_correct += is_prediction_correct.sum().item()\n",
    "            # count the total evaluations, the first dimension of `labels` is `batch_size`\n",
    "            total += labels.size(0)\n",
    "\n",
    "    print(f\"num_correct: {num_correct}, total: {total}\")\n",
    "    accuracy = float(num_correct) / float(total)\n",
    "    return accuracy\n",
    "\n",
    "\n",
    "accuracy = check_accuracy(model, data_loader)\n",
    "\n",
    "print(f\"Test Accuracy of the model: {accuracy*100:.2f}%\")\n",
    "\n",
    "# デバッグ情報を追加して再度確認\n",
    "for data, label in data_loader:\n",
    "    output = model(data)\n",
    "    label = label.view(-1)  # ラベルの形状を修正\n",
    "    loss = loss_func(output, label)\n",
    "    print(f\"Data: {data}, Label: {label}, Output: {output}, Loss: {loss.item()}\")\n",
    "    break  # 最初のバッチのみを確認\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66eae25d-cccc-412b-b968-1af7574794a3",
   "metadata": {},
   "source": [
    "## Reload model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 209,
   "id": "8ce58b0f-714e-41b4-8e95-7b0be6080a8b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model loaded from trained_model.pth\n",
      "predictions: tensor([0., 0., 1., 1.], requires_grad=True)\n",
      "labels:      tensor([0., 0., 1., 1.])\n",
      "num_correct: 4, total: 4\n",
      "Test Accuracy of the model: 100.00%\n"
     ]
    }
   ],
   "source": [
    "# 新しいモデルインスタンスの作成\n",
    "loaded_model = Net()\n",
    "\n",
    "# モデルの読み込み\n",
    "loaded_model.load_state_dict(torch.load(\"trained_model.pth\"))\n",
    "print(\"Model loaded from trained_model.pth\")\n",
    "\n",
    "# モデルのテスト\n",
    "accuracy = check_accuracy(model, data_loader)\n",
    "print(f\"Test Accuracy of the model: {accuracy*100:.2f}%\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
